package org.hibernate.impl;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.transaction.TransactionManager;
import org.hibernate.AssertionFailure;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.MappingException;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.QueryException;
import org.hibernate.SessionFactory;
import org.hibernate.SessionFactoryObserver;
import org.hibernate.StatelessSession;
import org.hibernate.cache.CacheKey;
import org.hibernate.cache.CollectionRegion;
import org.hibernate.cache.EntityRegion;
import org.hibernate.cache.QueryCache;
import org.hibernate.cache.Region;
import org.hibernate.cache.UpdateTimestampsCache;
import org.hibernate.cache.access.AccessType;
import org.hibernate.cache.access.CollectionRegionAccessStrategy;
import org.hibernate.cache.access.EntityRegionAccessStrategy;
import org.hibernate.cache.impl.CacheDataDescriptionImpl;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Settings;
import org.hibernate.classic.Session;
import org.hibernate.connection.ConnectionProvider;
import org.hibernate.context.CurrentSessionContext;
import org.hibernate.context.JTASessionContext;
import org.hibernate.context.ManagedSessionContext;
import org.hibernate.context.ThreadLocalSessionContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.SQLFunctionRegistry;
import org.hibernate.engine.FilterDefinition;
import org.hibernate.engine.Mapping;
import org.hibernate.engine.NamedQueryDefinition;
import org.hibernate.engine.NamedSQLQueryDefinition;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.query.QueryPlanCache;
import org.hibernate.engine.query.sql.NativeSQLQuerySpecification;
import org.hibernate.event.EventListeners;
import org.hibernate.exception.SQLExceptionConverter;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.UUIDHexGenerator;
import org.hibernate.jdbc.BatcherFactory;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.RootClass;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.metadata.CollectionMetadata;
import org.hibernate.persister.PersisterFactory;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.EntityNotFoundDelegate;
import org.hibernate.stat.Statistics;
import org.hibernate.stat.StatisticsImpl;
import org.hibernate.stat.StatisticsImplementor;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.hibernate.tool.hbm2ddl.SchemaValidator;
import org.hibernate.transaction.TransactionFactory;
import org.hibernate.type.AssociationType;
import org.hibernate.type.Type;
import org.hibernate.util.CollectionHelper;
import org.hibernate.util.ReflectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressWarnings("unchecked")
public final class SessionFactoryImpl implements SessionFactory,
		SessionFactoryImplementor {

	/**
	 * 
	 */
	private static final long serialVersionUID = -1340296471845266023L;

	private static final Logger log = LoggerFactory
			.getLogger(SessionFactoryImpl.class);
	private static final IdentifierGenerator UUID_GENERATOR = new UUIDHexGenerator();
	private final String name;
	private final String uuid;
	private final transient Map entityPersisters = new HashMap();
	private final transient Map classMetadata;
	private final transient Map collectionPersisters = new HashMap();
	private final transient Map collectionMetadata;
	private final transient Map collectionRolesByEntityParticipant;
	private final transient Map identifierGenerators = new HashMap();
	private final transient Map namedQueries;
	private final transient Map namedSqlQueries;
	private final transient Map sqlResultSetMappings;
	private final transient Map filters = new HashMap();
	private final transient Map imports;
	private final transient Interceptor interceptor;
	private final transient Settings settings;
	private final transient Properties properties = new Properties();
	private transient SchemaExport schemaExport;
	private final transient TransactionManager transactionManager;
	private final transient QueryCache queryCache;
	private final transient UpdateTimestampsCache updateTimestampsCache;
	private final transient Map queryCaches;
	private final transient Map allCacheRegions = new HashMap();
	private final transient StatisticsImpl statistics = new StatisticsImpl(this);
	private final transient EventListeners eventListeners;
	private final transient CurrentSessionContext currentSessionContext = buildCurrentSessionContext();
	private final transient EntityNotFoundDelegate entityNotFoundDelegate;
	private final transient SQLFunctionRegistry sqlFunctionRegistry;
	private final transient SessionFactoryObserver observer;
	private final QueryPlanCache queryPlanCache = new QueryPlanCache(this);
	private transient boolean isClosed;

	public SessionFactoryImpl(Configuration cfg, Mapping mapping,
			Settings settings, EventListeners listeners,
			SessionFactoryObserver observer) throws HibernateException {
		isClosed = false;
		log.info("building session factory");
		properties.putAll(cfg.getProperties());
		interceptor = cfg.getInterceptor();
		this.settings = settings;
		sqlFunctionRegistry = new SQLFunctionRegistry(settings.getDialect(),
				cfg.getSqlFunctions());
		eventListeners = listeners;
		this.observer = observer == null ? new SessionFactoryObserver() {

			/**
			 * 
			 */
			private static final long serialVersionUID = 0L;

			public void sessionFactoryCreated(SessionFactory sessionfactory) {
			}

			public void sessionFactoryClosed(SessionFactory sessionfactory) {
			}
		} : observer;
		filters.putAll(cfg.getFilterDefinitions());
		if (log.isDebugEnabled())
			log
					.debug("Session factory constructed with filter configurations : "
							+ filters);
		if (log.isDebugEnabled())
			log.debug("instantiating session factory with properties: "
					+ properties);
		settings.getRegionFactory().start(settings, properties);
		Iterator classes = cfg.getClassMappings();
		do {
			if (!classes.hasNext())
				break;
			PersistentClass model = (PersistentClass) classes.next();
			if (!model.isInherited()) {
				IdentifierGenerator generator = model.getIdentifier()
						.createIdentifierGenerator(settings.getDialect(),
								settings.getDefaultCatalogName(),
								settings.getDefaultSchemaName(),
								(RootClass) model);
				identifierGenerators.put(model.getEntityName(), generator);
			}
		} while (true);
		String cacheRegionPrefix = settings.getCacheRegionPrefix() != null ? settings
				.getCacheRegionPrefix()
				+ "."
				: "";
		Map entityAccessStrategies = new HashMap();
		Map classMeta = new HashMap();
		PersistentClass model;
		EntityPersister cp;
		for (classes = cfg.getClassMappings(); classes.hasNext(); classMeta
				.put(model.getEntityName(), cp.getClassMetadata())) {
			model = (PersistentClass) classes.next();
			model.prepareTemporaryTables(mapping, settings.getDialect());
			String cacheRegionName = cacheRegionPrefix
					+ model.getRootClass().getCacheRegionName();
			EntityRegionAccessStrategy accessStrategy = (EntityRegionAccessStrategy) entityAccessStrategies
					.get(cacheRegionName);
			if (accessStrategy == null && settings.isSecondLevelCacheEnabled()) {
				AccessType accessType = AccessType.parse(model
						.getCacheConcurrencyStrategy());
				if (accessType != null) {
					log.trace("Building cache for entity data ["
							+ model.getEntityName() + "]");
					EntityRegion entityRegion = settings.getRegionFactory()
							.buildEntityRegion(cacheRegionName, properties,
									CacheDataDescriptionImpl.decode(model));
					accessStrategy = entityRegion
							.buildAccessStrategy(accessType);
					entityAccessStrategies.put(cacheRegionName, accessStrategy);
					allCacheRegions.put(cacheRegionName, entityRegion);
				}
			}
			cp = PersisterFactory.createClassPersister(model, accessStrategy,
					this, mapping);
			entityPersisters.put(model.getEntityName(), cp);
		}

		classMetadata = Collections.unmodifiableMap(classMeta);
		Map tmpEntityToCollectionRoleMap = new HashMap();
		Iterator collections = cfg.getCollectionMappings();
		do {
			if (!collections.hasNext())
				break;
			Collection collection = (Collection) collections.next();
			String cacheRegionName = cacheRegionPrefix
					+ collection.getCacheRegionName();
			AccessType accessType = AccessType.parse(collection
					.getCacheConcurrencyStrategy());
			CollectionRegionAccessStrategy accessStrategy = null;
			if (accessType != null && settings.isSecondLevelCacheEnabled()) {
				log.trace("Building cache for collection data ["
						+ collection.getRole() + "]");
				CollectionRegion collectionRegion = settings.getRegionFactory()
						.buildCollectionRegion(cacheRegionName, properties,
								CacheDataDescriptionImpl.decode(collection));
				accessStrategy = collectionRegion
						.buildAccessStrategy(accessType);
				entityAccessStrategies.put(cacheRegionName, accessStrategy);
				allCacheRegions.put(cacheRegionName, collectionRegion);
			}
			CollectionPersister persister = PersisterFactory
					.createCollectionPersister(cfg, collection, accessStrategy,
							this);
			collectionPersisters.put(collection.getRole(), persister
					.getCollectionMetadata());
			Type indexType = persister.getIndexType();
			if (indexType != null && indexType.isAssociationType()
					&& !indexType.isAnyType()) {
				String entityName = ((AssociationType) indexType)
						.getAssociatedEntityName(this);
				Set roles = (Set) tmpEntityToCollectionRoleMap.get(entityName);
				if (roles == null) {
					roles = new HashSet();
					tmpEntityToCollectionRoleMap.put(entityName, roles);
				}
				roles.add(persister.getRole());
			}
			Type elementType = persister.getElementType();
			if (elementType.isAssociationType() && !elementType.isAnyType()) {
				String entityName = ((AssociationType) elementType)
						.getAssociatedEntityName(this);
				Set roles = (Set) tmpEntityToCollectionRoleMap.get(entityName);
				if (roles == null) {
					roles = new HashSet();
					tmpEntityToCollectionRoleMap.put(entityName, roles);
				}
				roles.add(persister.getRole());
			}
		} while (true);
		collectionMetadata = Collections.unmodifiableMap(collectionPersisters);
		java.util.Map.Entry entry;
		for (Iterator itr = tmpEntityToCollectionRoleMap.entrySet().iterator(); itr
				.hasNext(); entry.setValue(Collections
				.unmodifiableSet((Set) entry.getValue())))
			entry = (java.util.Map.Entry) itr.next();

		collectionRolesByEntityParticipant = Collections
				.unmodifiableMap(tmpEntityToCollectionRoleMap);
		namedQueries = new HashMap(cfg.getNamedQueries());
		namedSqlQueries = new HashMap(cfg.getNamedSQLQueries());
		sqlResultSetMappings = new HashMap(cfg.getSqlResultSetMappings());
		imports = new HashMap(cfg.getImports());
		for (Iterator iter = entityPersisters.values().iterator(); iter
				.hasNext(); ((EntityPersister) iter.next()).postInstantiate())
			;
		for (Iterator iter = collectionPersisters.values().iterator(); iter
				.hasNext(); ((CollectionPersister) iter.next())
				.postInstantiate())
			;
		name = settings.getSessionFactoryName();
		try {
			uuid = (String) UUID_GENERATOR.generate(null, null);
		} catch (Exception e) {
			throw new AssertionFailure("Could not generate UUID");
		}
		SessionFactoryObjectFactory.addInstance(uuid, name, this, properties);
		log.debug("instantiated session factory");
		if (settings.isAutoCreateSchema())
			(new SchemaExport(cfg, settings)).create(false, true);
		if (settings.isAutoUpdateSchema())
			(new SchemaUpdate(cfg, settings)).execute(false, true);
		if (settings.isAutoValidateSchema())
			(new SchemaValidator(cfg, settings)).validate();
		if (settings.isAutoDropSchema())
			schemaExport = new SchemaExport(cfg, settings);
		if (settings.getTransactionManagerLookup() != null) {
			log.debug("obtaining JTA TransactionManager");
			transactionManager = settings.getTransactionManagerLookup()
					.getTransactionManager(properties);
		} else {
			if (settings.getTransactionFactory().isTransactionManagerRequired())
				throw new HibernateException(
						"The chosen transaction strategy requires access to the JTA TransactionManager");
			transactionManager = null;
		}
		if (settings.isQueryCacheEnabled()) {
			updateTimestampsCache = new UpdateTimestampsCache(settings,
					properties);
			queryCache = settings.getQueryCacheFactory().getQueryCache(null,
					updateTimestampsCache, settings, properties);
			queryCaches = new HashMap();
			allCacheRegions.put(updateTimestampsCache.getRegion().getName(),
					updateTimestampsCache.getRegion());
			allCacheRegions.put(queryCache.getRegion().getName(), queryCache
					.getRegion());
		} else {
			updateTimestampsCache = null;
			queryCache = null;
			queryCaches = null;
		}
		if (settings.isNamedQueryStartupCheckingEnabled()) {
			Map errors = checkNamedQueries();
			if (!errors.isEmpty()) {
				Set keys = errors.keySet();
				StringBuffer failingQueries = new StringBuffer(
						"Errors in named queries: ");
				String queryName;
				HibernateException e;
				for (Iterator iterator = keys.iterator(); iterator.hasNext(); log
						.error("Error in named query: " + queryName, e)) {
					queryName = (String) iterator.next();
					e = (HibernateException) errors.get(queryName);
					failingQueries.append(queryName);
					if (iterator.hasNext())
						failingQueries.append(", ");
				}

				throw new HibernateException(failingQueries.toString());
			}
		}
		getStatistics().setStatisticsEnabled(settings.isStatisticsEnabled());
		EntityNotFoundDelegate entityNotFoundDelegate = cfg
				.getEntityNotFoundDelegate();
		if (entityNotFoundDelegate == null)
			entityNotFoundDelegate = new EntityNotFoundDelegate() {

				public void handleEntityNotFound(String entityName,
						Serializable id) {
					throw new ObjectNotFoundException(id, entityName);
				}
			};
		this.entityNotFoundDelegate = entityNotFoundDelegate;
		this.observer.sessionFactoryCreated(this);
	}

	public void addPersistentClass(PersistentClass model, Mapping mapping) {
		String cacheRegionPrefix = settings.getCacheRegionPrefix() != null ? settings
				.getCacheRegionPrefix()
				+ "."
				: "";
		Map entityAccessStrategies = new HashMap();
		model.prepareTemporaryTables(mapping, settings.getDialect());
		String cacheRegionName = cacheRegionPrefix
				+ model.getRootClass().getCacheRegionName();
		EntityRegionAccessStrategy accessStrategy = (EntityRegionAccessStrategy) entityAccessStrategies
				.get(cacheRegionName);
		if (accessStrategy == null && settings.isSecondLevelCacheEnabled()) {
			AccessType accessType = AccessType.parse(model
					.getCacheConcurrencyStrategy());
			if (accessType != null) {
				log.trace("Building cache for entity data ["
						+ model.getEntityName() + "]");
				EntityRegion entityRegion = settings.getRegionFactory()
						.buildEntityRegion(cacheRegionName, properties,
								CacheDataDescriptionImpl.decode(model));
				accessStrategy = entityRegion.buildAccessStrategy(accessType);
				entityAccessStrategies.put(cacheRegionName, accessStrategy);
				allCacheRegions.put(cacheRegionName, entityRegion);
			}
		}
		EntityPersister cp = PersisterFactory.createClassPersister(model, accessStrategy, this,
				mapping);
		entityPersisters.put(model.getEntityName(), cp);
	}

	public QueryPlanCache getQueryPlanCache() {
		return queryPlanCache;
	}

	private Map checkNamedQueries() throws HibernateException {
		Map errors = new HashMap();
		log.debug("Checking " + namedQueries.size() + " named HQL queries");
		for (Iterator itr = namedQueries.entrySet().iterator(); itr.hasNext();) {
			java.util.Map.Entry entry = (java.util.Map.Entry) itr.next();
			String queryName = (String) entry.getKey();
			NamedQueryDefinition qd = (NamedQueryDefinition) entry.getValue();
			try {
				log.debug("Checking named query: " + queryName);
				queryPlanCache.getHQLQueryPlan(qd.getQueryString(), false,
						CollectionHelper.EMPTY_MAP);
			} catch (QueryException e) {
				errors.put(queryName, e);
			} catch (MappingException e) {
				errors.put(queryName, e);
			}
		}

		log.debug("Checking " + namedSqlQueries.size() + " named SQL queries");
		for (Iterator itr = namedSqlQueries.entrySet().iterator(); itr
				.hasNext();) {
			java.util.Map.Entry entry = (java.util.Map.Entry) itr.next();
			String queryName = (String) entry.getKey();
			NamedSQLQueryDefinition qd = (NamedSQLQueryDefinition) entry
					.getValue();
			try {
				log.debug("Checking named SQL query: " + queryName);
				NativeSQLQuerySpecification spec;
				if (qd.getResultSetRef() != null) {
					ResultSetMappingDefinition definition = (ResultSetMappingDefinition) sqlResultSetMappings
							.get(qd.getResultSetRef());
					if (definition == null)
						throw new MappingException(
								"Unable to find resultset-ref definition: "
										+ qd.getResultSetRef());
					spec = new NativeSQLQuerySpecification(qd.getQueryString(),
							definition.getQueryReturns(), qd.getQuerySpaces());
				} else {
					spec = new NativeSQLQuerySpecification(qd.getQueryString(),
							qd.getQueryReturns(), qd.getQuerySpaces());
				}
				queryPlanCache.getNativeSQLQueryPlan(spec);
			} catch (QueryException e) {
				errors.put(queryName, e);
			} catch (MappingException e) {
				errors.put(queryName, e);
			}
		}

		return errors;
	}

	public StatelessSession openStatelessSession() {
		return new StatelessSessionImpl(null, this);
	}

	public StatelessSession openStatelessSession(Connection connection) {
		return new StatelessSessionImpl(connection, this);
	}

	private SessionImpl openSession(Connection connection, boolean autoClose,
			long timestamp, Interceptor sessionLocalInterceptor) {
		return new SessionImpl(connection, this, autoClose, timestamp,
				sessionLocalInterceptor != null ? sessionLocalInterceptor
						: interceptor, settings.getDefaultEntityMode(),
				settings.isFlushBeforeCompletionEnabled(), settings
						.isAutoCloseSessionEnabled(), settings
						.getConnectionReleaseMode());
	}

	public Session openSession(Connection connection,
			Interceptor sessionLocalInterceptor) {
		return openSession(connection, false, 0x8000000000000000L,
				sessionLocalInterceptor);
	}

	public Session openSession(Interceptor sessionLocalInterceptor)
			throws HibernateException {
		long timestamp = settings.getRegionFactory().nextTimestamp();
		return openSession(null, true, timestamp, sessionLocalInterceptor);
	}

	public Session openSession(Connection connection) {
		return openSession(connection, interceptor);
	}

	public Session openSession() throws HibernateException {
		return openSession(interceptor);
	}

	public Session openTemporarySession() throws HibernateException {
		return new SessionImpl(null, this, true, settings.getRegionFactory()
				.nextTimestamp(), interceptor, settings.getDefaultEntityMode(),
				false, false, ConnectionReleaseMode.AFTER_STATEMENT);
	}

	public Session openSession(Connection connection,
			boolean flushBeforeCompletionEnabled,
			boolean autoCloseSessionEnabled,
			ConnectionReleaseMode connectionReleaseMode)
			throws HibernateException {
		return new SessionImpl(connection, this, true, settings
				.getRegionFactory().nextTimestamp(), interceptor, settings
				.getDefaultEntityMode(), flushBeforeCompletionEnabled,
				autoCloseSessionEnabled, connectionReleaseMode);
	}

	public Session getCurrentSession() throws HibernateException {
		if (currentSessionContext == null)
			throw new HibernateException("No CurrentSessionContext configured!");
		else
			return currentSessionContext.currentSession();
	}

	public EntityPersister getEntityPersister(String entityName)
			throws MappingException {
		EntityPersister result = (EntityPersister) entityPersisters
				.get(entityName);
		if (result == null)
			throw new MappingException("Unknown entity: " + entityName);
		else
			return result;
	}

	public CollectionPersister getCollectionPersister(String role)
			throws MappingException {
		CollectionPersister result = (CollectionPersister) collectionPersisters
				.get(role);
		if (result == null)
			throw new MappingException("Unknown collection role: " + role);
		else
			return result;
	}

	public Settings getSettings() {
		return settings;
	}

	public Dialect getDialect() {
		return settings.getDialect();
	}

	public Interceptor getInterceptor() {
		return interceptor;
	}

	public TransactionFactory getTransactionFactory() {
		return settings.getTransactionFactory();
	}

	public TransactionManager getTransactionManager() {
		return transactionManager;
	}

	public SQLExceptionConverter getSQLExceptionConverter() {
		return settings.getSQLExceptionConverter();
	}

	public Set getCollectionRolesByEntityParticipant(String entityName) {
		return (Set) collectionRolesByEntityParticipant.get(entityName);
	}

	public Reference getReference() throws NamingException {
		log.debug("Returning a Reference to the SessionFactory");
		return new Reference((org.hibernate.impl.SessionFactoryImpl.class)
				.getName(), new StringRefAddr("uuid", uuid),
				(org.hibernate.impl.SessionFactoryObjectFactory.class)
						.getName(), null);
	}

	private Object readResolve() throws ObjectStreamException {
		log.trace("Resolving serialized SessionFactory");
		Object result = SessionFactoryObjectFactory.getInstance(uuid);
		if (result == null) {
			result = SessionFactoryObjectFactory.getNamedInstance(name);
			if (result == null)
				throw new InvalidObjectException(
						"Could not find a SessionFactory named: " + name);
			log.debug("resolved SessionFactory by name");
		} else {
			log.debug("resolved SessionFactory by uid");
		}
		return result;
	}

	public NamedQueryDefinition getNamedQuery(String queryName) {
		return (NamedQueryDefinition) namedQueries.get(queryName);
	}

	public NamedSQLQueryDefinition getNamedSQLQuery(String queryName) {
		return (NamedSQLQueryDefinition) namedSqlQueries.get(queryName);
	}

	public ResultSetMappingDefinition getResultSetMapping(String resultSetName) {
		return (ResultSetMappingDefinition) sqlResultSetMappings
				.get(resultSetName);
	}

	public Type getIdentifierType(String className) throws MappingException {
		return getEntityPersister(className).getIdentifierType();
	}

	public String getIdentifierPropertyName(String className)
			throws MappingException {
		return getEntityPersister(className).getIdentifierPropertyName();
	}

	private void readObject(ObjectInputStream in) throws IOException,
			ClassNotFoundException {
		log.trace("deserializing");
		in.defaultReadObject();
		log.debug("deserialized: " + uuid);
	}

	private void writeObject(ObjectOutputStream out) throws IOException {
		log.debug("serializing: " + uuid);
		out.defaultWriteObject();
		log.trace("serialized");
	}

	public Type[] getReturnTypes(String queryString) throws HibernateException {
		return queryPlanCache.getHQLQueryPlan(queryString, false,
				CollectionHelper.EMPTY_MAP).getReturnMetadata()
				.getReturnTypes();
	}

	public String[] getReturnAliases(String queryString)
			throws HibernateException {
		return queryPlanCache.getHQLQueryPlan(queryString, false,
				CollectionHelper.EMPTY_MAP).getReturnMetadata()
				.getReturnAliases();
	}

	public ClassMetadata getClassMetadata(Class persistentClass)
			throws HibernateException {
		return getClassMetadata(persistentClass.getName());
	}

	public CollectionMetadata getCollectionMetadata(String roleName)
			throws HibernateException {
		return (CollectionMetadata) collectionMetadata.get(roleName);
	}

	public ClassMetadata getClassMetadata(String entityName)
			throws HibernateException {
		return (ClassMetadata) classMetadata.get(entityName);
	}

	public String[] getImplementors(String className) throws MappingException {
		Class clazz;
		try {
			clazz = ReflectHelper.classForName(className);
		} catch (ClassNotFoundException cnfe) {
			return (new String[] { className });
		}
		ArrayList results = new ArrayList();
		Iterator iter = entityPersisters.values().iterator();
		do {
			if (!iter.hasNext())
				break;
			EntityPersister testPersister = (EntityPersister) iter.next();
			if (testPersister instanceof Queryable) {
				Queryable testQueryable = (Queryable) testPersister;
				String testClassName = testQueryable.getEntityName();
				boolean isMappedClass = className.equals(testClassName);
				if (testQueryable.isExplicitPolymorphism()) {
					if (isMappedClass)
						return (new String[] { className });
				} else if (isMappedClass) {
					results.add(testClassName);
				} else {
					Class mappedClass = testQueryable
							.getMappedClass(EntityMode.POJO);
					if (mappedClass != null
							&& clazz.isAssignableFrom(mappedClass)) {
						boolean assignableSuperclass;
						if (testQueryable.isInherited()) {
							Class mappedSuperclass = getEntityPersister(
									testQueryable.getMappedSuperclass())
									.getMappedClass(EntityMode.POJO);
							assignableSuperclass = clazz
									.isAssignableFrom(mappedSuperclass);
						} else {
							assignableSuperclass = false;
						}
						if (!assignableSuperclass)
							results.add(testClassName);
					}
				}
			}
		} while (true);
		return (String[]) (String[]) results
				.toArray(new String[results.size()]);
	}

	public String getImportedClassName(String className) {
		String result = (String) imports.get(className);
		if (result == null)
			try {
				ReflectHelper.classForName(className);
				return className;
			} catch (ClassNotFoundException cnfe) {
				return null;
			}
		else
			return result;
	}

	public Map getAllClassMetadata() throws HibernateException {
		return classMetadata;
	}

	public Map getAllCollectionMetadata() throws HibernateException {
		return collectionMetadata;
	}

	public void close() throws HibernateException {
		if (isClosed) {
			log.trace("already closed");
			return;
		}
		log.info("closing");
		isClosed = true;
		Iterator iter = entityPersisters.values().iterator();
		do {
			if (!iter.hasNext())
				break;
			EntityPersister p = (EntityPersister) iter.next();
			if (p.hasCache())
				p.getCacheAccessStrategy().getRegion().destroy();
		} while (true);
		iter = collectionPersisters.values().iterator();
		do {
			if (!iter.hasNext())
				break;
			CollectionPersister p = (CollectionPersister) iter.next();
			if (p.hasCache())
				p.getCacheAccessStrategy().getRegion().destroy();
		} while (true);
		if (settings.isQueryCacheEnabled()) {
			queryCache.destroy();
			QueryCache cache;
			for (iter = queryCaches.values().iterator(); iter.hasNext(); cache
					.destroy())
				cache = (QueryCache) iter.next();

			updateTimestampsCache.destroy();
		}
		settings.getRegionFactory().stop();
		if (settings.isAutoDropSchema())
			schemaExport.drop(false, true);
		try {
			settings.getConnectionProvider().close();
		} finally {
			SessionFactoryObjectFactory.removeInstance(uuid, name, properties);
		}
		observer.sessionFactoryClosed(this);
		eventListeners.destroyListeners();
	}

	public void evictEntity(String entityName, Serializable id)
			throws HibernateException {
		EntityPersister p = getEntityPersister(entityName);
		if (p.hasCache()) {
			if (log.isDebugEnabled())
				log.debug("evicting second-level cache: "
						+ MessageHelper.infoString(p, id, this));
			CacheKey cacheKey = new CacheKey(id, p.getIdentifierType(), p
					.getRootEntityName(), EntityMode.POJO, this);
			p.getCacheAccessStrategy().evict(cacheKey);
		}
	}

	public void evictEntity(String entityName) throws HibernateException {
		EntityPersister p = getEntityPersister(entityName);
		if (p.hasCache()) {
			if (log.isDebugEnabled())
				log.debug("evicting second-level cache: " + p.getEntityName());
			p.getCacheAccessStrategy().evictAll();
		}
	}

	public void evict(Class persistentClass, Serializable id)
			throws HibernateException {
		EntityPersister p = getEntityPersister(persistentClass.getName());
		if (p.hasCache()) {
			if (log.isDebugEnabled())
				log.debug("evicting second-level cache: "
						+ MessageHelper.infoString(p, id, this));
			CacheKey cacheKey = new CacheKey(id, p.getIdentifierType(), p
					.getRootEntityName(), EntityMode.POJO, this);
			p.getCacheAccessStrategy().evict(cacheKey);
		}
	}

	public void evict(Class persistentClass) throws HibernateException {
		EntityPersister p = getEntityPersister(persistentClass.getName());
		if (p.hasCache()) {
			if (log.isDebugEnabled())
				log.debug("evicting second-level cache: " + p.getEntityName());
			p.getCacheAccessStrategy().evictAll();
		}
	}

	public void evictCollection(String roleName, Serializable id)
			throws HibernateException {
		CollectionPersister p = getCollectionPersister(roleName);
		if (p.hasCache()) {
			if (log.isDebugEnabled())
				log.debug("evicting second-level cache: "
						+ MessageHelper.collectionInfoString(p, id, this));
			CacheKey cacheKey = new CacheKey(id, p.getKeyType(), p.getRole(),
					EntityMode.POJO, this);
			p.getCacheAccessStrategy().evict(cacheKey);
		}
	}

	public void evictCollection(String roleName) throws HibernateException {
		CollectionPersister p = getCollectionPersister(roleName);
		if (p.hasCache()) {
			if (log.isDebugEnabled())
				log.debug("evicting second-level cache: " + p.getRole());
			p.getCacheAccessStrategy().evictAll();
		}
	}

	public Type getReferencedPropertyType(String className, String propertyName)
			throws MappingException {
		return getEntityPersister(className).getPropertyType(propertyName);
	}

	public ConnectionProvider getConnectionProvider() {
		return settings.getConnectionProvider();
	}

	public UpdateTimestampsCache getUpdateTimestampsCache() {
		return updateTimestampsCache;
	}

	public QueryCache getQueryCache() {
		return queryCache;
	}

	public QueryCache getQueryCache(String regionName)
			throws HibernateException {
		if (regionName == null)
			return getQueryCache();
		if (!settings.isQueryCacheEnabled())
			return null;
		try {
			QueryCache currentQueryCache = (QueryCache) queryCaches
					.get(regionName);
			if (currentQueryCache == null) {
				currentQueryCache = settings.getQueryCacheFactory()
						.getQueryCache(regionName, updateTimestampsCache,
								settings, properties);
				queryCaches.put(regionName, currentQueryCache);
				allCacheRegions.put(currentQueryCache.getRegion().getName(),
						currentQueryCache.getRegion());
			}
			return currentQueryCache;
		} catch (Exception exception) {
			throw new HibernateException(exception);
		}
	}

	public Region getSecondLevelCacheRegion(String regionName) {
		try {
			return (Region) allCacheRegions.get(regionName);
		} catch (Exception exception) {
			return null;
		}
	}

	public Map getAllSecondLevelCacheRegions() {
		try {
			return new HashMap(allCacheRegions);
		} catch (Exception exception) {
			return null;
		}
	}

	public boolean isClosed() {
		return isClosed;
	}

	public Statistics getStatistics() {
		return statistics;
	}

	public StatisticsImplementor getStatisticsImplementor() {
		return statistics;
	}

	public void evictQueries() throws HibernateException {
		if (settings.isQueryCacheEnabled())
			queryCache.clear();
	}

	public void evictQueries(String cacheRegion) throws HibernateException {
		if (cacheRegion == null)
			throw new NullPointerException(
					"use the zero-argument form to evict the default query cache");
		synchronized (allCacheRegions) {
			if (settings.isQueryCacheEnabled()) {
				QueryCache currentQueryCache = (QueryCache) queryCaches
						.get(cacheRegion);
				if (currentQueryCache != null)
					currentQueryCache.clear();
			}
		}
	}

	public FilterDefinition getFilterDefinition(String filterName)
			throws HibernateException {
		FilterDefinition def = (FilterDefinition) filters.get(filterName);
		if (def == null)
			throw new HibernateException("No such filter configured ["
					+ filterName + "]");
		else
			return def;
	}

	public Set getDefinedFilterNames() {
		return filters.keySet();
	}

	public BatcherFactory getBatcherFactory() {
		return settings.getBatcherFactory();
	}

	public IdentifierGenerator getIdentifierGenerator(String rootEntityName) {
		return (IdentifierGenerator) identifierGenerators.get(rootEntityName);
	}

	private CurrentSessionContext buildCurrentSessionContext() {
		String impl = properties
				.getProperty("hibernate.current_session_context_class");
		if (impl == null && transactionManager != null)
			impl = "jta";
		if (impl == null)
			return null;
		if ("jta".equals(impl)) {
			if (settings.getTransactionFactory()
					.areCallbacksLocalToHibernateTransactions())
				log
						.warn("JTASessionContext being used with JDBCTransactionFactory; auto-flush will not operate correctly with getCurrentSession()");
			return new JTASessionContext(this);
		}
		if ("thread".equals(impl))
			return new ThreadLocalSessionContext(this);
		if ("managed".equals(impl))
			return new ManagedSessionContext(this);
		try {
			Class implClass = ReflectHelper.classForName(impl);
			return (CurrentSessionContext) implClass
					.getConstructor(
							new Class[] { org.hibernate.engine.SessionFactoryImplementor.class })
					.newInstance(new Object[] { this });
		} catch (Throwable t) {
			log.error("Unable to construct current session context [" + impl
					+ "]", t);
		}
		return null;
	}

	public EventListeners getEventListeners() {
		return eventListeners;
	}

	public EntityNotFoundDelegate getEntityNotFoundDelegate() {
		return entityNotFoundDelegate;
	}

	void serialize(ObjectOutputStream oos) throws IOException {
		oos.writeUTF(uuid);
		oos.writeBoolean(name != null);
		if (name != null)
			oos.writeUTF(name);
	}

	static SessionFactoryImpl deserialize(ObjectInputStream ois)
			throws IOException, ClassNotFoundException {
		String uuid = ois.readUTF();
		boolean isNamed = ois.readBoolean();
		String name = null;
		if (isNamed)
			name = ois.readUTF();
		Object result = SessionFactoryObjectFactory.getInstance(uuid);
		if (result == null) {
			log.trace("could not locate session factory by uuid [" + uuid
					+ "] during session deserialization; trying name");
			if (isNamed)
				result = SessionFactoryObjectFactory.getNamedInstance(name);
			if (result == null)
				throw new InvalidObjectException(
						"could not resolve session factory during session deserialization [uuid="
								+ uuid + ", name=" + name + "]");
		}
		return (SessionFactoryImpl) result;
	}

	public SQLFunctionRegistry getSqlFunctionRegistry() {
		return sqlFunctionRegistry;
	}

}
